using Fusion.Frameworks.Assets.Editor;
using Fusion.Frameworks.Editor;
using Fusion.Hotfix.Mono.Editor;
using Fusion.Utilities.IO;
using ILRuntime.Runtime.Enviorment;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;

namespace Fusion.Hotfix.Editor
{
    public class DLLPacker
    {
        private static List<string> scriptPaths = new List<string>();
        private static DLLSetting dllSetting = null;
        private static string customGeneratedPath = "Assets/Scripts/Generated";
        private static string customCLRBindingPath = "Assets/Scripts/Generated/CLRBinding";
        private static string customAdapterPath = "Assets/Scripts/Generated/Adapters";
        private static string monoDataScriptPath = "Assets/Scripts/MonoData";
        private static string monoDataPath = "Assets/GameAssets/MonoData";
        private static string monoDataAssetsPath = "Assets/MonoData/Editor/MonoAssets";
        private static string monoDataEditorPath = "Assets/MonoData/Editor/Scripts";
        private static string dllOutput = "FusionTemp/DLL/Extra.dll";

        private static string dynamicEntableKey = "Fusion_DynamicDLLEnabled";

        private static string productParentFolder = "Assets/GameAssets/Scripts";
        private static string productOutput = "Assets/GameAssets/Scripts/Extra.bytes";

        static DLLPacker()
        {
            string settingName = $"{AssetsPacker.FilePathPrefix}/DLLSetting.asset";
            dllSetting = AssetDatabase.LoadAssetAtPath<DLLSetting>(settingName);
            if (dllSetting == null)
            {
                dllSetting = ScriptableObject.CreateInstance<DLLSetting>();
                AssetDatabase.CreateAsset(dllSetting, settingName);
                AssetDatabase.Refresh();
            }
        }

        [MenuItem("DLLManager/GenerateMonoData", false, 500)]
        private static void GenerateMonoData()
        {
            if (Directory.Exists(monoDataEditorPath))
            {
                string[] files = Directory.GetFiles(monoDataEditorPath);

                if (!Directory.Exists(monoDataScriptPath))
                {
                    Directory.CreateDirectory(monoDataScriptPath);
                }
                else
                {
                    string[] scripts = Directory.GetFiles(monoDataScriptPath);
                    for (int i = 0; i < scripts.Length; i++)
                    {
                        if (scripts[i].EndsWith(".cs"))
                        {
                            File.Delete(scripts[i]);
                        }
                    }
                }

                for (int i = 0; i < files.Length; i++)
                {
                    if (files[i].EndsWith(".cs"))
                    {
                        FileInfo fileInfo = new FileInfo(files[i]);
                        string fileContext = IOUtility.Read(fileInfo.FullName);
                        fileContext = fileContext.Replace("_MonoDataEditor", "");
                        int createAssetMenuBeigin = fileContext.IndexOf("[CreateAssetMenu");
                        if (createAssetMenuBeigin >= 0)
                        {
                            string createAssetMenu = fileContext.Substring(createAssetMenuBeigin, fileContext.IndexOf("]", createAssetMenuBeigin) - createAssetMenuBeigin + 1);
                            fileContext = fileContext.Replace(createAssetMenu, "");
                        }

                        fileContext = fileContext.Replace("using Fusion.Hotfix.Mono.Editor;", "using Fusion.Hotfix.Mono;");
                        fileContext = fileContext.Replace("ScriptableMonoData", "DLLMonoData");
                        IOUtility.Write($"{monoDataScriptPath}/{fileInfo.Name}", fileContext);
                    }
                }

                if (Directory.Exists(monoDataAssetsPath))
                {

                    string[] assets = Directory.GetFiles(monoDataAssetsPath);
                    if (!Directory.Exists(monoDataPath))
                    {
                        Directory.CreateDirectory(monoDataPath);
                    }
                    else
                    {
                        string[] monoDatas = Directory.GetFiles(monoDataPath);
                        for (int i = 0; i < monoDatas.Length; i++)
                        {
                            if (monoDatas[i].EndsWith(".json"))
                            {
                                File.Delete(monoDatas[i]);
                            }
                        }
                    }
                    for (int i = 0; i < assets.Length; i++)
                    {
                        if (assets[i].EndsWith(".asset"))
                        {
                            FileInfo assetInfo = new FileInfo(assets[i]);
                            ScriptableMonoData smd = AssetDatabase.LoadAssetAtPath<ScriptableMonoData>(assets[i]);
                            IOUtility.Write($"{monoDataPath}/{assetInfo.Name.Replace(".asset", ".json")}", smd.ToString());
                        }
                    }
                }

                Debug.Log("MonoData生成成功！");
                AssetDatabase.Refresh();
            }
            else
            {
                Debug.Log("无MonoData文件");
            }
        }

        [MenuItem("DLLManager/SwichDynamicDLLState/Enabled", true)]
        public static bool DynamicDLLHandle()
        {
            bool currentEnabled = Utility.ContainScriptingDefineSymbol("FUSION_DYNAMIC_DLL");
            if (Menu.GetChecked("DLLManager/SwichDynamicDLLState/Enabled") != currentEnabled)
            {
                bool dynamicDLLEnabled = currentEnabled;
                EditorPrefs.SetBool(dynamicEntableKey, dynamicDLLEnabled);
                Menu.SetChecked("DLLManager/SwichDynamicDLLState/Enabled", dynamicDLLEnabled);
            }

            return true;
        }

        [MenuItem("DLLManager/SwichDynamicDLLState/Enabled", false, 500)]
        private static void DynamicDLLSetter()
        {
            bool dynamicDLLEnabled = EditorPrefs.GetBool(dynamicEntableKey, false);
            dynamicDLLEnabled = !dynamicDLLEnabled;
            EditorPrefs.SetBool(dynamicEntableKey, dynamicDLLEnabled);
            if (dynamicDLLEnabled)
            {
                SwichDynamicDLLEnabled();
            }
            else
            {
                SwichDynamicDLLDisabled();
            }
        }

        private static void SwichDynamicDLLEnabled()
        {
            Utility.AppendScriptingDefineSymbol("FUSION_DYNAMIC_DLL");
        }

        private static void SwichDynamicDLLDisabled()
        {
            Utility.DeleteScriptingDefineSymbol("FUSION_DYNAMIC_DLL");
        }

        [MenuItem("DLLManager/GenerateCLRBinding", false, 200)]
        static void GenerateCLRBindingByAnalysis()
        {
            ILRuntime.Runtime.Enviorment.AppDomain appDomain = new ILRuntime.Runtime.Enviorment.AppDomain();
            using (System.IO.FileStream fs = new System.IO.FileStream(dllOutput, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                appDomain.LoadAssembly(fs);
                Type classType = Type.GetType("Fusion.Hotfix.DLLCustomBinder, Assembly-CSharp");
                DLLBinder dllBinder = classType != null ? (DLLBinder)Activator.CreateInstance(classType, appDomain) : new DLLBinder(appDomain);
                dllBinder.Register();
                ILRuntime.Runtime.CLRBinding.BindingCodeGenerator.GenerateBindingCode(appDomain, customCLRBindingPath);
            }

            AssetDatabase.Refresh();
        }

        [MenuItem("DLLManager/GenerateCustomBinder", false, 200)]
        public static void GenerateCustomBinder()
        {
            if (AssetDatabase.IsValidFolder(customAdapterPath))
            {
                AssetDatabase.DeleteAsset(customAdapterPath);
            }
            StringBuilder dllCustomBinder = new StringBuilder();
            StringBuilder importString = new StringBuilder(@"using ILRuntime.Runtime.Enviorment;");

            StringBuilder adapters = null;
            if (dllSetting.CustomAdapters != null && dllSetting.CustomAdapters.Length > 0)
            {

                adapters = new StringBuilder(@"
        public override void RegisterAdapters()
        {
            base.RegisterAdapters();");
                Directory.CreateDirectory(customAdapterPath);

                for (int index = 0; index < dllSetting.CustomAdapters.Length; index++)
                {
                    string className = dllSetting.CustomAdapters[index];
                    string[] classNameAndAssemblyName = className.Split(",");
                    if (classNameAndAssemblyName.Length == 1)
                    {
                        className = $"{className},Assembly-CSharp";
                    }
                    Type classType = Type.GetType(className);

                    string namePrefix = classNameAndAssemblyName[0].Substring(classNameAndAssemblyName[0].LastIndexOf(".") + 1);
                    IOUtility.Write($"{customAdapterPath}/{namePrefix}Adapter.cs", CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(classType, "Fusion.Hotfix.Adapters"));
                    adapters.Append(@$"
            appDomain.RegisterCrossBindingAdaptor(new {namePrefix}Adapter());");
                }
                adapters.Append(@"
        }");
            }

            StringBuilder valueTypeBinders = null;
            if (dllSetting.ValueTypeBinders != null && dllSetting.ValueTypeBinders.Count > 0)
            {
                valueTypeBinders = new StringBuilder(@"
        public override void RegisterValueTypeBinders()
        {
            base.RegisterValueTypeBinders();");
                for (int index = 0; index < dllSetting.ValueTypeBinders.Count; index++)
                {
                    ValueTypeBinderData delegateData = dllSetting.ValueTypeBinders[index];
                    valueTypeBinders.Append(@$"
            appDomain.RegisterValueTypeBinder(typeof({delegateData.originType}), new {delegateData.binder}());");
                }
                valueTypeBinders.Append(@"
        }");
            }

            StringBuilder methodDelegates = null;
            if (dllSetting.CustomMethodDelegates != null && dllSetting.CustomMethodDelegates.Count > 0)
            {
                methodDelegates = new StringBuilder(@"
        public override void RegisterMethodDelegate()
        {
            base.RegisterMethodDelegate();");
                for (int index = 0; index < dllSetting.CustomMethodDelegates.Count; index++)
                {
                    DelegateData delegateData = dllSetting.CustomMethodDelegates[index];
                    StringBuilder className = new StringBuilder(delegateData.data[0]);
                    for (int subIndex = 1; subIndex < delegateData.data.Count; subIndex++)
                    {
                        className.Append($", {delegateData.data[subIndex]}");
                    }
                    methodDelegates.Append(@$"
            appDomain.DelegateManager.RegisterMethodDelegate<{className}>();");
                }
                methodDelegates.Append(@"
        }");
            }

            StringBuilder functionDelegates = null;
            if (dllSetting.CustomFunctionDelegates != null && dllSetting.CustomFunctionDelegates.Count > 0)
            {
                functionDelegates = new StringBuilder(@"
        public override void RegisterFunctionDelegate()
        {
            base.RegisterFunctionDelegate();");
                for (int index = 0; index < dllSetting.CustomFunctionDelegates.Count; index++)
                {
                    DelegateData delegateData = dllSetting.CustomFunctionDelegates[index];
                    StringBuilder className = new StringBuilder(delegateData.data[0]);
                    for (int subIndex = 1; subIndex < delegateData.data.Count; subIndex++)
                    {
                        className.Append($", {delegateData.data[subIndex]}");
                    }
                    functionDelegates.Append(@$"
            appDomain.DelegateManager.RegisterFunctionDelegate<{className}>();");
                }
                functionDelegates.Append(@"
        }");
            }

            StringBuilder delegateConvertors = null;
            if (dllSetting.CustomDelegateConvertors != null && dllSetting.CustomDelegateConvertors.Count > 0)
            {
                delegateConvertors = new StringBuilder(@"
        public override void RegisterDelegateConvertor()
        {
            base.RegisterDelegateConvertor();");

                for (int index = 0; index < dllSetting.CustomDelegateConvertors.Count; index++)
                {
                    DelegateConvertorData delegateConvertorData = dllSetting.CustomDelegateConvertors[index];
                    string delegateFullName = delegateConvertorData.delegateFullName;
                    string originDelegateType = $"System.{delegateConvertorData.delegateType}";
                    StringBuilder className = new StringBuilder();
                    StringBuilder paramsList = new StringBuilder();

                    string returnPrefix = delegateConvertorData.delegateType == DelegateType.Func ? "return " : "";

                    if (delegateConvertorData.data != null && delegateConvertorData.data.Count > 0)
                    {
                        className.Append($"<{delegateConvertorData.data[0]}");
                        paramsList.Append("x0");
                        int totalParamCount = delegateConvertorData.delegateType == DelegateType.Func ? delegateConvertorData.data.Count - 1 : delegateConvertorData.data.Count;
                        for (int subIndex = 1; subIndex < delegateConvertorData.data.Count; subIndex++)
                        {
                            className.Append($", {delegateConvertorData.data[subIndex]}");
                            if (subIndex <= totalParamCount - 1)
                            {
                                paramsList.Append($", x{subIndex}");
                            }
                        }
                        className.Append(">");
                    }

                    delegateConvertors.Append(@$"
            appDomain.DelegateManager.RegisterDelegateConvertor<{delegateFullName}>((action) =>
            {{
                return new {delegateFullName}(({paramsList}) =>
                {{
                    {returnPrefix}(({originDelegateType}{className})action)({paramsList});
                }});
            }});");

                }
                delegateConvertors.Append(@"
        }");
            }

            dllCustomBinder.AppendLine(importString.ToString());
            dllCustomBinder.AppendLine(@"
namespace Fusion.Hotfix
{
    public class DLLCustomBinder : DLLBinder
    {
        public DLLCustomBinder(AppDomain appDomain) : base(appDomain)
        {
        }");
            if (adapters != null)
            {
                dllCustomBinder.AppendLine(adapters.ToString());
            }

            if (valueTypeBinders != null)
            {
                dllCustomBinder.AppendLine(valueTypeBinders.ToString());
            }

            if (methodDelegates != null)
            {
                dllCustomBinder.AppendLine(methodDelegates.ToString());
            }

            if (functionDelegates != null)
            {
                dllCustomBinder.AppendLine(functionDelegates.ToString());
            }

            if (delegateConvertors != null)
            {
                dllCustomBinder.AppendLine(delegateConvertors.ToString());
            }

            dllCustomBinder.AppendLine(@"
        public override void RegisterCLRBinding()
        {
            base.RegisterCLRBinding();
            ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);
        }");
            dllCustomBinder.AppendLine("    }");
            dllCustomBinder.AppendLine("}");

            if (!Directory.Exists(customGeneratedPath))
            {
                Directory.CreateDirectory(customGeneratedPath);
            }

            IOUtility.Write($"{customGeneratedPath}/DLLCustomBinder.cs", dllCustomBinder.ToString());

            string uiGenerator = @"
using Fusion.Frameworks.UI;

namespace Fusion.Hotfix.Generator
{
    public class UIGenerator : IUIGenerator
    {
        public UIObject Generate(string name, UIData data)
        {
            return DLLManager.Instance.Instantiate<UIObject>(name, data);
        }
    }
}
            ";
            IOUtility.Write($"{customGeneratedPath}/UIGenerator.cs", uiGenerator);

            AssetDatabase.Refresh();
        }

        [MenuItem("DLLManager/Build", false, 2000)]
        public static void Build()
        {
            if (dllSetting.ScriptsForPack == null)
            {
                return;
            }

            scriptPaths.Clear();
            for (int i = 0; i < dllSetting.ScriptsForPack.Length; i++)
            {
                string path = AssetDatabase.GetAssetPath(dllSetting.ScriptsForPack[i]);
                if (AssetDatabase.IsValidFolder(path))
                {
                    CollectCSharpFile(path);
                }
            }

            if (scriptPaths.Count == 0)
            {
                return;
            }

            if (!Directory.Exists("FusionTemp/DLL"))
            {
                Directory.CreateDirectory("FusionTemp/DLL");
            }

            AssemblyBuilder assemblyBuilder = new AssemblyBuilder(dllOutput, scriptPaths.ToArray());
            assemblyBuilder.referencesOptions = ReferencesOptions.UseEngineModules;
            assemblyBuilder.compilerOptions.AllowUnsafeCode = dllSetting.AllowUnsafeCode;
            if (dllSetting.AdditionalReferences != null && dllSetting.AdditionalReferences.Length > 0)
            {
                string[] AdditionalReferences = new string[dllSetting.AdditionalReferences.Length];
                for (int index = 0; index < dllSetting.AdditionalReferences.Length; index++)
                {
                    string path = Application.dataPath.Replace("\\", "/");
                    string prefix = path.Substring(0, path.LastIndexOf("/") + 1);
                    AdditionalReferences[index] = $"{prefix}{dllSetting.AdditionalReferences[index]}";
                }
                assemblyBuilder.additionalReferences = AdditionalReferences;
            }

            assemblyBuilder.buildFinished += delegate (string assemblyPath, CompilerMessage[] compilerMessages)
            {
                var errorCount = compilerMessages.Count(m => m.type == CompilerMessageType.Error);
                var warningCount = compilerMessages.Count(m => m.type == CompilerMessageType.Warning);

                Debug.LogFormat("Assembly build finished for {0}", assemblyPath);
                Debug.LogFormat("Warnings: {0} - Errors: {0}", errorCount, warningCount);

                for (int i = 0; i < compilerMessages.Length; i++)
                {
                    if (compilerMessages[i].type == CompilerMessageType.Error)
                    {
                        Debug.LogError(compilerMessages[i].message);
                    }
                    else
                    {
                        Debug.LogWarning(compilerMessages[i].message);
                    }
                }

                if (errorCount == 0)
                {
                    if (!Directory.Exists(productParentFolder))
                    {
                        Directory.CreateDirectory(productParentFolder);
                        AssetDatabase.ImportAsset(productParentFolder);

                    }
                    File.Copy(dllOutput, productOutput, true);
                    AssetDatabase.ImportAsset(productOutput);
                }
            };
            assemblyBuilder.Build();
            while (assemblyBuilder.status != AssemblyBuilderStatus.Finished)
            {

            }

            GenerateCustomBinder();
            GenerateCLRBindingByAnalysis();

            GenerateMonoData();
            AssetDatabase.Refresh();
        }

        [MenuItem("DLLManager/BuildToAssetsBundle", false, 1900)]
        public static void BuildToAssetsBundle()
        {
            Build();
            BuildAssetBundleOptions compressType = (BuildAssetBundleOptions)Builder.BuildSetting.compressType;
            BuildAssetBundleOptions optionsAll = compressType;

            AssetBundleBuild[] assetBundleBuilds = new AssetBundleBuild[1];
            assetBundleBuilds[0] = new AssetBundleBuild();
            string fileName = "Extra" + AssetsPacker.AssetBundleSuffix;
            assetBundleBuilds[0].assetBundleName = "Scripts/" + fileName;
            assetBundleBuilds[0].assetNames = new string[] { productOutput };
            BuildPipeline.BuildAssetBundles("FusionTemp/DLL", assetBundleBuilds, optionsAll, Builder.GetCurrentBuildTarget());

            string bundleTargetPath = Application.streamingAssetsPath + "/ManagedAssets/scripts/";
            if (!Directory.Exists(bundleTargetPath))
            {
                Directory.CreateDirectory(bundleTargetPath);
            }
            File.Copy("FusionTemp/DLL/scripts/" + fileName, bundleTargetPath + fileName.ToLower(), true);

            AssetDatabase.Refresh();
        }

        private static void CollectCSharpFile(string path)
        {
            string[] fileList = Directory.GetFiles(path);

            for (int i = 0; i < fileList.Length; i++)
            {
                FileInfo fileInfo = new FileInfo(fileList[i]);

                if (CheckFileValid(fileInfo))
                {
                    string filePath = fileList[i].Replace("/", "/");
                    if (!scriptPaths.Contains(filePath))
                    {
                        scriptPaths.Add(filePath);
                    }
                }
            }

            string[] dirList = Directory.GetDirectories(path);
            for (int index = 0; index < dirList.Length; index++)
            {
                CollectCSharpFile(dirList[index]);
            }
        }

        private static bool CheckFileValid(FileInfo fileInfo)
        {
            if ((fileInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
                return false;
            return fileInfo.Name.EndsWith(".cs");
        }

        private static List<string> ScriptsForPackPath = new List<string>();
        public static void BackupCSharp()
        {
#if FUSION_DYNAMIC_DLL
            ScriptsForPackPath.Clear();
            if (dllSetting.ScriptsForPack == null)
                return;
            for (int i = 0; i < dllSetting.ScriptsForPack.Length; i++)
            {
                string path = Path.GetFullPath(AssetDatabase.GetAssetPath(dllSetting.ScriptsForPack[i]));
                ScriptsForPackPath.Add(path);
                File.Move($"{path}.meta", $"{path}.meta.bak");
                Directory.Move(path, $"{path}~");
            }

            AssetDatabase.Refresh();
#endif
        }

        public static void RecoverCSharp()
        {
#if FUSION_DYNAMIC_DLL
            for (int i = 0; i < ScriptsForPackPath.Count; i++)
            {
                string path = ScriptsForPackPath[i];
                Directory.Move($"{path}~", path);
                File.Move($"{path}.meta.bak", $"{path}.meta");
            }
            AssetDatabase.Refresh();
#endif
        }
    }

}

